/** ****************************************************************************
  Copyright 2012 Progress Software Corporation
  
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
  
    http://www.apache.org/licenses/LICENSE-2.0
  
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
**************************************************************************** **/
/** ------------------------------------------------------------------------
    File        : Stack
    Purpose     : A stack is a last-in-first-out collection. 
    Syntax      : 
    Description : 
    @author pjudge
    Created     : Tue Jan 05 15:09:51 EST 2010
    Notes       : * Non-char primitive values will have their own IntegerStack() say,that calls PrimitivePush and
                    converts to/from character to their native datatype. 
                  * The Stack temp-table could probably be dynamic, and have a single value field that
                    is dynamically constructed, but stacks need to be lightweight at runtime (IMO anyway). 
  ---------------------------------------------------------------------- */
routine-level on error undo, throw.

using OpenEdge.Lang.Collections.Stack.
using OpenEdge.Lang.Collections.StackError.
using OpenEdge.Lang.DataTypeEnum.
using OpenEdge.Lang.IOModeEnum.
using OpenEdge.Lang.Assert.
using Progress.Lang.ParameterList.
using Progress.Lang.Object.

class OpenEdge.Lang.Collections.Stack abstract:
    define public static variable DEFAULT_STACK_DEPTH as integer no-undo.
    
    /** NOTE/ the expand/shrink behaviour could be handled with an enumeration,
        but as of 10.2B performance is better when using logical variables.
     **/
    /* Keep incrementally growing stack Depth as new elements are added.
       This will negatively impact performance. */
    define public property AutoExpand as logical no-undo get. set.
    
    /* If true, we'll discard stuff off the bottom of the stack if
       we resize the stack smaller than its contents. */
    define public property DiscardOnShrink as logical no-undo get. set.
    
    define public property Depth as integer no-undo 
        get.
        set (piDepth as int):
            SetStackDepth(piDepth).
            Depth = piDepth.
        end set.
    
    define public property Size as integer no-undo get. protected set.
    
    define private variable mcPrimitiveStack as character extent no-undo.
    define private variable moObjectStack as Object extent no-undo.
    define private variable mlObjectStack as logical no-undo.
    
    constructor protected Stack(poArray as Object extent):
        define variable iLoop as integer no-undo.
        
        this-object(extent(poArray)).
        
        /* The Size may not equal Depth for the input array, especially
           if this is a Stack being cloned. */        
        do iLoop = 1 to Depth while valid-object(poArray[iLoop]):
            this-object:ObjectPush(poArray[iLoop]).
        end.
    end constructor.
    
    constructor protected Stack(pcArray as character extent):
        this-object(extent(pcArray)).
        
        define variable iLoop as integer no-undo.
        do iLoop = 1 to Depth:
            this-object:PrimitivePush(pcArray[iLoop]).
        end.
    end constructor.
    
    constructor static Stack():
        Stack:DEFAULT_STACK_DEPTH = 10.
    end constructor.
    
    constructor public Stack(piDepth as int):
        assign Depth = piDepth
               mlObjectStack = ?
               AutoExpand = false
               DiscardOnShrink = false.
    end constructor.
    
    constructor public Stack():
        this-object(Stack:DEFAULT_STACK_DEPTH).
    end constructor.
    
    method private void SetStackDepth(piDepth as int):
        define variable cTempPrimitive as character extent no-undo.
        define variable oTempObject as Object extent no-undo.
        define variable iLoop as integer no-undo.
        define variable iMax as integer no-undo.
        
        /* do nothing if there's nothing to do */
        if piDepth ne Depth then
        do:
            if piDepth lt Size and not DiscardOnShrink then
                undo, throw new StackError(StackError:RESIZE).
            
            /* On init there's no need for any of this. */
            if Depth eq 0 then
                assign iMax = 0.
            else
                assign extent(oTempObject) = Depth
                       extent(cTempPrimitive) = Depth
                       oTempObject = moObjectStack
                       cTempPrimitive = mcPrimitiveStack
                       extent(moObjectStack) = ?
                       extent(mcPrimitiveStack) = ?
                       iMax = Size.
            
            assign extent(moObjectStack) = piDepth
                   extent(mcPrimitiveStack) = piDepth
                   Size = 0.
            
            /* On init this loop won't execute */
            do iLoop = 1 to iMax:
                /* A stack can only be one or the other ... */
                if mlObjectStack then
                    ObjectPush(oTempObject[iLoop]).
                else
                    PrimitivePush(cTempPrimitive[iLoop]).
            end.
        end.
    end method.
    
    method protected void ObjectPush(poValue as Object):
        Assert:ArgumentNotNull(poValue, 'Value').
        
        if Size eq Depth then
        do:
            if AutoExpand then 
                Depth = Size + round(0.5 * Size, 0).
            else
                undo, throw new StackError(StackError:OVERFLOW).
        end.
        
        assign Size = Size + 1
               moObjectStack[Size] = poValue
               mlObjectStack = true when mlObjectStack eq ?.
    end method.
    
    method protected Object ObjectPeek():
        /* Size should never be <= 0 but hey! ... */
        if Size le 0 then
            undo, throw new StackError(StackError:UNDERFLOW).
        
        if Size gt Depth then
            undo, throw new StackError(StackError:OVERFLOW).
        
        return moObjectStack[Size].
    end method.
    
    method protected Object ObjectPop():
        define variable oValue as Object no-undo.
        
        assign oValue = ObjectPeek()
               /* clean out necessary even though Size defines what's on top, 
                  so that we don't leak by holding a reference */
               moObjectStack[Size] = ?
               Size = Size - 1
               .
        return oValue.
    end method.    

    method protected void PrimitivePush(pcValue as character):
        if Size eq Depth then
        do:
            if AutoExpand then 
                Depth = Size + round(0.5 * Size, 0).
            else
                undo, throw new StackError(StackError:OVERFLOW).
        end.
        
        assign Size = Size + 1
               mcPrimitiveStack[Size] = pcValue
               mlObjectStack = false when mlObjectStack eq ?.
    end method.
    
    method protected character PrimitivePeek():
        /* Size should never be < 0 but hey! ... */
        if Size eq 0 then
            undo, throw new StackError(StackError:UNDERFLOW).
                        
        if Size gt Depth then
            undo, throw new StackError(StackError:OVERFLOW).
        
        return mcPrimitiveStack[Size].        
    end method.
    
    method protected character PrimitivePop():
        define variable cValue as character no-undo.
        
        assign cValue = PrimitivePeek()
               /* clean out not totally necessary, since Size defines what's on top */
               mcPrimitiveStack[Size] = ?   
               Size = Size - 1.
        
        return cValue.
    end method.
    
    method public void Swap(piFromPos as integer, piToPos as integer):
        define variable cTempPrimitive as character no-undo.
        define variable oTempObject as Object no-undo.

        if piFromPos eq piToPos then
            return.
        
        /* keep the first item */        
        if mlObjectStack then
            assign oTempObject = moObjectStack[piFromPos]
                   moObjectStack[piFromPos] = moObjectStack[piToPos]
                   moObjectStack[piToPos] = oTempObject.
        else
            assign cTempPrimitive = mcPrimitiveStack[piFromPos]
                   mcPrimitiveStack[piFromPos] = mcPrimitiveStack[piToPos]
                   mcPrimitiveStack[piToPos] = cTempPrimitive.
    end method.
    
    method public void Rotate(piItems as integer):
        define variable cTempPrimitive as character no-undo.
        define variable oTempObject as Object no-undo.
        define variable iLoop as integer no-undo.
        
        if piItems le 1 then
            return.
        
        /* keep the first item */        
        if mlObjectStack then
            oTempObject = moObjectStack[1].
        else
            cTempPrimitive = mcPrimitiveStack[1]. 
        
        do iLoop = 1 to piItems - 1:
            if mlObjectStack then
                moObjectStack[iLoop] = moObjectStack[iLoop + 1].
            else
                mcPrimitiveStack[iLoop] = mcPrimitiveStack[iLoop + 1]. 
        end.
        
        /* put the first item at the bottom of the rotation */
        if mlObjectStack then
            moObjectStack[piItems] = oTempObject.
        else
            mcPrimitiveStack[piItems] = cTempPrimitive.
    end method.
    
    method protected Object extent ObjectToArray():
        return moObjectStack.
    end method.

    method protected character extent PrimitiveToArray():
        return mcPrimitiveStack.        
    end method.
    
    method public void Invert():
        define variable cTempPrimitive as character extent no-undo.
        define variable oTempObject as Object extent no-undo.
        define variable iLoop as integer no-undo.
        
        /* No point, really */
        if Size le 1 then
            return.
        
        /* keep the first item */
        if mlObjectStack then
            oTempObject = moObjectStack.
        else            
            cTempPrimitive = mcPrimitiveStack.
        
        do iLoop = 1 to Size:
            if mlObjectStack then
                moObjectStack[iLoop] = oTempObject[Size - iLoop + 1].
            else
                mcPrimitiveStack[iLoop] = cTempPrimitive[Size - iLoop + 1].
        end.
    end method.
    
    method public override Object Clone():
        define variable oParams as ParameterList no-undo.
        
        oParams = new ParameterList(1).
        if Size eq 0 then
            oParams:SetParameter(1,
                                 DataTypeEnum:Integer:ToString(),
                                 IOModeEnum:Input:ToString(),
                                 this-object:Depth).
        else
        if mlObjectStack then
            oParams:SetParameter(1,
                                 substitute(DataTypeEnum:ClassArray:ToString(), DataTypeEnum:ProgressLangObject:ToString()),  
                                 IOModeEnum:Input:ToString(),
                                 this-object:ObjectToArray() ).
        else
            oParams:SetParameter(1,
                                 DataTypeEnum:CharacterArray:ToString(),
                                 IOModeEnum:Input:ToString(),
                                 this-object:PrimitiveToArray() ).
        
        return this-object:GetClass():New(oParams).
    end method.
    
    method protected logical ObjectContains(poItem as Object):
        define variable lContains as logical no-undo.
        define variable iLoop as integer no-undo.
        
        lContains = false.
        do iLoop = Size to 1 by -1 while not lContains:
            lContains = moObjectStack[iLoop]:Equals(poItem).
        end.
        
        return lContains.
    end method.
    
    method protected logical PrimitiveContains(pcItem as character):
        define variable lContains as logical no-undo.
        define variable iLoop as integer no-undo.

        lContains = false.
        do iLoop = Size to 1 by -1 while not lContains:
            lContains = mcPrimitiveStack[iLoop] eq pcItem.
        end.
        
        return lContains.
    end method.
    
end class.
